/*
 *  Copyright © 2011 by Rob
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 * DeliciousServiceProvider.java
 *
 * Created on Jun 22, 2011 at 11:06:53 PM
 */
package org.robcash.apps.bookmarksync.provider.spi.delicious;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.robcash.apps.bookmarksync.AuthenticationException;
import org.robcash.apps.bookmarksync.AuthorizationException;
import org.robcash.apps.bookmarksync.BookmarkSyncException;
import org.robcash.apps.bookmarksync.provider.BookmarkServiceDescriptor;
import org.robcash.apps.bookmarksync.provider.BookmarkServiceProvider;
import org.robcash.apps.bookmarksync.provider.spi.BaseBookmarkServiceProvider;
import org.robcash.apps.bookmarksync.vo.Bookmark;
import org.robcash.apps.bookmarksync.vo.Tag;
import org.robcash.commons.plugin.PlugInException;
import org.robcash.commons.plugin.annotation.Pluggable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import del.icio.us.Delicious;
import del.icio.us.DeliciousConstants;
import del.icio.us.DeliciousNotAuthorizedException;
import del.icio.us.beans.Post;

/**
 * BookmarkSync service provider for Delicious bookmarks.
 *
 * @author Rob Cash
 */
@Pluggable(name = "Delicious")
@Qualifier("deliciousServiceProvider")
public class DeliciousServiceProvider extends BaseBookmarkServiceProvider
		implements BookmarkServiceProvider
{

	@SuppressWarnings("unused")
	private static final Logger LOG = LoggerFactory
			.getLogger(DeliciousServiceProvider.class);

	/**
	 * Service descriptor. This indicates what the service is and is not capable
	 * of doing.
	 */
	@Autowired
	@Qualifier("deliciousServiceDescriptor")
	private BookmarkServiceDescriptor serviceDescriptor;

	/**
	 * Service interface.
	 */
	private Delicious service;

	/**
	 * Bookmarks loaded from service.
	 */
	private final ArrayList<Bookmark> bookmarks;

	/**
	 * Tags loaded from service.
	 */
	private final ArrayList<Tag> tags;

	/**
	 * Map of tag name to bookmarks.
	 */
	private Map<String, List<Bookmark>> bookmarkMap;

	/**
	 * Map of tag name to tags.
	 */
	private Map<String, Tag> tagNameMap;

	/**
	 * Indicates if data has been loaded.
	 */
	private boolean dataLoaded;

	public DeliciousServiceProvider()
	{
		this.bookmarks = new ArrayList<Bookmark>();
		this.tags = new ArrayList<Tag>();
		this.bookmarkMap = new HashMap<String, List<Bookmark>>();
		this.tagNameMap = new HashMap<String, Tag>();
	}

	/**
	 * Get the Delicious service
	 *
	 * @return Delicious service
	 */
	public Delicious getService()
	{
		return service;
	}

	/*
	 * (non-Javadoc)
	 * @see org.robcash.commons.plugin.PlugIn#onInit()
	 */
	@Override
	public void onInit()
	{
		// Nothing to do
	}

	/*
	 * (non-Javadoc)
	 * @see org.robcash.commons.plugin.PlugIn#onRegister()
	 */
	@Override
	public void onRegister() throws PlugInException
	{
		// Nothing to do
	}

	/*
	 * (non-Javadoc)
	 * @see org.robcash.commons.plugin.PlugIn#onUnregister()
	 */
	@Override
	public void onUnregister() throws PlugInException
	{
		// Nothing to do
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public BookmarkServiceDescriptor getServiceDescriptor()
	{
		return serviceDescriptor;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isUserLoggedIn()
	{
		// User is logged in if the service is not null
		return service != null;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void login(final String userId, final String password) throws BookmarkSyncException
	{
		try
		{
			service = new Delicious(userId, password,
					DeliciousConstants.API_ENDPOINT);// , "localhost", 8888);
			// Get something -- anything, really -- to test if user ID & password were correct
			service.getBundles();
		}
		catch(DeliciousNotAuthorizedException ex)
		{
			throw new AuthenticationException("Delicious authentication failed", ex);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void logout() throws BookmarkSyncException
	{
		service = null;
		dataLoaded = false;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<Bookmark> getBookmarks() throws BookmarkSyncException
	{
		loadData();
		return bookmarks;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<Bookmark> getBookmarksWithTag(final Tag tag)
			throws BookmarkSyncException
	{
		return bookmarkMap.get(tag.getName());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<Tag> getTags() throws BookmarkSyncException
	{
		loadData();
		return tags;
	}


	/**
	 * {@inheritDoc}
	 * @throws UnsupportedOperationException This is always thrown. This provider
	 * does not support creating bookmarks.
	 */
	@Override
	public Bookmark createBookmark(final Bookmark bookmark)
			throws BookmarkSyncException
	{
		throw new UnsupportedOperationException("Provider does not support creating bookmarks");
	}

	/**
	 * {@inheritDoc}
	 * @throws UnsupportedOperationException This is always thrown. This provider
	 * does not support updating bookmarks.
	 */
	@Override
	public Bookmark updateBookmark(final Bookmark bookmark)
			throws BookmarkSyncException
	{
		throw new UnsupportedOperationException("Provider does not support updating bookmarks");
	}

	/**
	 * {@inheritDoc}
	 * @throws UnsupportedOperationException This is always thrown. This provider
	 * does not support deleting bookmarks.
	 */
	@Override
	public void deleteBookmark(final Bookmark bookmark) throws BookmarkSyncException
	{
		throw new UnsupportedOperationException("Provider does not support deleting bookmarks");
	}

	public Tag getTagForName(final String name)
	{
		return tagNameMap.get(name);
	}

	/**
	 * Check to ensure that the user is logged in. If they are not, throw
	 * an {@link AuthorizationException|
	 * @throws AuthorizationException Thrown if the user is not logged in
	 */
	private void checkLoginStatus() throws AuthorizationException
	{
		if (!isUserLoggedIn())
		{
			throw new AuthorizationException("User is not logged in");
		}
	}

	private synchronized void loadData() throws BookmarkSyncException
	{
		if (dataLoaded)
		{
			return;
		}
		checkLoginStatus();

		// Load bookmarks
		bookmarks.clear();
		for (Object post : service.getAllPosts())
		{
			bookmarks.add(new BookmarkFacade((Post) post, this));
		}

		// Load tags
		tags.clear();
		tagNameMap.clear();
		for (Object tag : service.getTags())
		{
			TagFacade facade = new TagFacade((del.icio.us.beans.Tag) tag, this);
			tags.add(facade);
			tagNameMap.put(facade.getName(), facade);
		}

		// Map bookmarks to tags
		bookmarkMap.clear();
		for (Bookmark bookmark : bookmarks)
		{
			// Get tag names for bookmark
			String[] tagNames = ((BookmarkFacade) bookmark).getPost()
					.getTagsAsArray("\\s+");
			for (int index = 0; index < tagNames.length; index++)
			{
				// Look at each tag for the bookmark
				String tagName = tagNames[index];

				// Get list of bookmarks for tag
				List<Bookmark> bookmarksForTag = bookmarkMap.get(tagName);
				if (bookmarksForTag == null)
				{
					// No bookmarks yet for this tag - create empty list
					bookmarksForTag = new ArrayList<Bookmark>();
					bookmarkMap.put(tagName, bookmarksForTag);
				}

				// Add bookmark to list of bookmarks for tag
				bookmarksForTag.add(bookmark);
			}
		}

		dataLoaded = true;
	}
}
